/*	
*	File: icmp-packet.h
*	Date: 2011/11/05
*	User: Ronmaru
*	Desctiption: icmp packet sender and receiver
*/

#include "pcraft-tool.h"

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <string>
#include <istream>
#include <iostream>
#include <ostream>

using boost::asio::ip::icmp;
using boost::asio::deadline_timer;
namespace posix_time = boost::posix_time;

class ICMPpacket
{
public:
  ICMPpacket(boost::asio::io_service& io_service, ICMPheader header, 
    const std::string &body, const std::string &destination)
    : icmp_resolver_(io_service), icmp_socket_(io_service, icmp::v4()), timer_(io_service), 
      icmp_header_(header), icmp_body_(body), encapsulate_ip_(1)
  {
    icmp::resolver::query query(icmp::v4(), destination.c_str(), "");
    icmp_destination_ = *icmp_resolver_.resolve(query);

    icmp_socket_.connect(icmp_destination_);
  }

  ICMPpacket(boost::asio::io_service& io_service, IPV4header ipv4_header, ICMPheader icmp_header,
    const std::string &body)
    : icmp_resolver_(io_service), icmp_socket_(io_service, icmp::v4()), timer_(io_service), 
    icmp_header_(icmp_header), ipv4_header_(ipv4_header), icmp_body_(body), encapsulate_ip_(0)
  {
    icmp::resolver::query query(icmp::v4(), ipv4_header.GetDestinationAddress().to_string(), "");
    icmp_destination_ = *icmp_resolver_.resolve(query);

    icmp_socket_.connect(icmp_destination_);
    const ip_hdrincl opt_ip_hdrincl(1);
    icmp_socket_.set_option(opt_ip_hdrincl);
  }

  void send()
  {
      boost::asio::streambuf request_buffer;
      std::ostream os(&request_buffer);
      ComputeChecksum(icmp_header_, icmp_body_.begin(), icmp_body_.end());
      if (encapsulate_ip_) {
        os << icmp_header_ << icmp_body_;
      } else {
        os << ipv4_header_ << icmp_header_ << icmp_body_;
      }
      
      icmp_socket_.send(request_buffer.data());

//       time_sent_ = posix_time::microsec_clock::universal_time();
//      
// 
//       // Wait up to five seconds for a reply.
//       num_replies_ = 0;
//       timer_.expires_at(time_sent_ + posix_time::seconds(5));
//       timer_.async_wait(boost::bind(&ICMPpacket::handle_timeout, this));
  }

  void handle_timeout()
  {
    if (num_replies_ == 0)
      std::cout << "Request timed out" << std::endl;

    // Requests must be sent no less than one second apart.
//     timer_.expires_at(time_sent_ + posix_time::seconds(1));
//     timer_.async_wait(boost::bind(&ICMPpacket::send, this));
  }

  void receive()
  {
    // Discard any data already in the buffer.
    reply_buffer_.consume(reply_buffer_.size());

    // Wait for a reply. We prepare the buffer to receive up to 64KB.
    icmp_socket_.async_receive(reply_buffer_.prepare(65536),
      boost::bind(&ICMPpacket::handle_receive, this, _2));
  }

  void handle_receive(std::size_t length)
  {
    // The actual number of bytes received is committed to the buffer so that we
    // can extract it using a std::istream object.
    reply_buffer_.commit(length);

    // Decode the reply packet.
    std::istream is(&reply_buffer_);
    IPV4header ipv4_hdr;
    ICMPheader icmp_hdr;
    is >> ipv4_hdr >> icmp_hdr;

    // We can receive all ICMP packets received by the host, so we need to
    // filter out only the echo replies that match the our identifier and
    // expected sequence number.
    if (is && icmp_hdr.GetType() == ICMPheader::echo_reply
      && icmp_hdr.GetIdentifier() == icmp_header_.GetIdentifier()
      && icmp_hdr.GetSequenceNumber() == icmp_header_.GetSequenceNumber())
    {
      // If this is the first reply, interrupt the five second timeout.
      if (num_replies_++ == 0)
        timer_.cancel();

      // Print out some information about the reply packet.
      posix_time::ptime now = posix_time::microsec_clock::universal_time();
      std::cout << length - ipv4_hdr.GetHeaderLength()
        << " bytes from " << ipv4_hdr.GetSourceAddress()
        << ": icmp_seq=" << icmp_hdr.GetSequenceNumber()
        << ", ttl=" << ipv4_hdr.GetTimeToLive()
        << ", time=" << (now - time_sent_).total_milliseconds() << " ms"
        << std::endl;
    }
  }

  int encapsulate_ip_;
  icmp::resolver icmp_resolver_;
  icmp::endpoint icmp_destination_;
  icmp::socket icmp_socket_;
  std::string icmp_body_;
  ICMPheader icmp_header_;
  IPV4header ipv4_header_;

  std::size_t num_replies_;
  deadline_timer timer_;
  posix_time::ptime time_sent_;
  boost::asio::streambuf reply_buffer_;
};